package com.game.Maze.Objects;

import java.util.ArrayList;
import java.util.Random;

import com.game.Maze.Util.WorldSide;

/**
 * Объект, который содержит информацию о лабиринте
 * 
 */
public class Maze implements IMaze
{
  private int m_Size; // Размер лабиринта
  Room m_Rooms[][];

  /**
   * Создает пустой лабиринт
   */
  private void createEmpty()
  {
    m_Rooms = new Room[size()][size()];

    int x, y;

    // Заполняем лабиринт комнатами
    for (x = 0; x < size(); x++)
      for (y = 0; y < size(); y++)
      {
        m_Rooms[x][y] = new Room();
      }

    // Связываем комнаты между собой
    for (x = 0; x < size(); x++)
      for (y = 0; y < size(); y++)
      {
        Room newRoom = room(x, y);
        newRoom.setRoomBySide(WorldSide.North, room(x, y + 1));
        newRoom.setRoomBySide(WorldSide.South, room(x, y - 1));
        newRoom.setRoomBySide(WorldSide.East, room(x + 1, y));
        newRoom.setRoomBySide(WorldSide.West, room(x - 1, y));
      }
  }

  /**
   * Создает выход из лабиринта
   * 
   * @param passagesInMaze список стен/проходов, используемый для создания лабиринта
   * @throws MazeException
   */
  private void generateExitRoom(ArrayList<Passage> passagesInMaze) throws MazeException
  {
    Random rand = new Random();

    // Выбираем стену на которой будет располагаться выход из лабиринта
    WorldSide exitSide = WorldSide.randomMainSide();
    int exitPos = rand.nextInt(size() - 1);
    Room exitRoom;

    // Получаем соответствующую комнату
    switch (exitSide)
    {
      case North:
      {
        exitRoom = room(exitPos, size() - 1);
        break;
      }

      case East:
      {
        exitRoom = room(size() - 1, exitPos);
        break;
      }

      case South:
      {
        exitRoom = room(exitPos, 0);
        break;
      }

      case West:
      {
        exitRoom = room(0, exitPos);
        break;
      }

      default:
        throw new MazeException("Не задана сторона выхода из лабиринта");
    }

    // Создаем проход являющийся выходом
    exitRoom.passageBySide(exitSide).makePassage();

    // Помечаем комнату как выход
    exitRoom.markAsExit(exitSide);

    // Помечаем комнату как используемую в лабиринте
    exitRoom.setUsed();

    // Добавляем все стены в список
    passagesInMaze.addAll(exitRoom.getAllWalls());
  }

  // Список стен, используемый при создании лабиринта
  private ArrayList<Passage> passagesInMaze = new ArrayList<Passage>();

  /**
   * Создает лабиринт
   * 
   * @throws MazeException
   */
  public void generate() throws MazeException
  {
    // Создаем выход
    generateExitRoom(passagesInMaze);

    Random rand = new Random();

    int randomPassageIndex;
    Passage selectedPassage;
    Room selectedRoom;
    Room oppositeRoom;

    while (!passagesInMaze.isEmpty())
    {
      // Получаем случайную стену или проход из списка
      if (passagesInMaze.size() > 2)
        randomPassageIndex = rand.nextInt(passagesInMaze.size() - 1);
      else
        randomPassageIndex = 0;

      selectedPassage = passagesInMaze.get(randomPassageIndex);

      if (selectedPassage.isWall())
      {
        // Получаем комнату к которой относится стена
        selectedRoom = selectedPassage.room();

        // Получаем комнату, находящуюся с другой стороны стены
        oppositeRoom = selectedPassage.oppositeRoom();

        // Проверяем что комната с другой стороны стены в лабиринт не входит в лабиринт
        if ((null != oppositeRoom) && (!oppositeRoom.isUsed()))
        {
          // Делаем проход вместо стены
          selectedRoom.makePassageBySide(selectedPassage.side());

          // Добавляем комнату, находящуюся с другой стороны в лабиринт
          oppositeRoom.setUsed();

          // Добавляем все стены этой комнаты в список стен/проходов
          passagesInMaze.addAll(oppositeRoom.getAllWalls());
        }
      }

      passagesInMaze.remove(randomPassageIndex);
    }
  }

  /**
   * Возвращает комнату с указанными координатами или null, если координаты выходят за пределы лабиринта.
   * 
   * @param aColumn столбец в котором расположена комната
   * @param aRow строка в которой расположена комната
   * @return комната или null
   */
  public Room room(int aColumn, int aRow)
  {
    if ((aColumn < 0) || (aRow < 0) || (aColumn >= size()) || (aRow >= size()))
      return null;

    return m_Rooms[aColumn][aRow];
  }

  public Maze(int mazeSize) throws MazeException
  {
    m_Size = mazeSize;
    createEmpty();
  }

  /**
   * @return ширина лабиринта в комнатах
   */
  public int size()
  {
    return m_Size;
  }
}
